/**
 * 
 */
package fr.univ_tln.inf9.exaplanning.api.db;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import fr.univ_tln.inf9.exaplanning.api.db.event.ObjectChangedEvent;
import fr.univ_tln.inf9.exaplanning.api.db.event.ObjectCreatedEvent;
import fr.univ_tln.inf9.exaplanning.api.db.event.ObjectListener;

/**
 * TODO: maintenant que l'exception EntityException est en place il faut nettoyer
 * tous les throws restants afin de rendre les erreurs de la base transparents.
 * Le SessionManager (gestionnaire de session) se charge d'effectuer le lien
 * entre l'application et le dataBaseManager, il conserve les objets
 * instanciables (qui étendent la classe AbstractEntity) qui ont été
 * modifiés ou crées pendant la session de travail pour permettre de
 * sauvegarder le travail effectué en conservant l'intégrité de la base.
 * Design Pattern du singleton.
 * @author emilien
 */
public class SessionManager implements ObjectListener {
	
	DataBaseManager dbManager;
	
	/**
	 * Instance du gestionnaire de session, est unique (singleton).
	 */
	private static SessionManager instance = null;
	
	/**
	 * Contient toutes les entitées qui doivent être synchronisées avec
	 * la base. La clef est l'objet en question auquel on associe
	 * l'entitée.
	 * @see Entity
	 */
	private Map<Object, Entity> entities;
	
	private SessionManager() throws IOException, SQLException {
		dbManager = DataBaseManager.getInstance();
		entities = new HashMap<Object, Entity>();
	}
	
	/**
	 * @return L'unique instance de la session en cours.
	 * Si celle si n'existe pas, on appelle le constructeur.
	 * @throws SQLException 
	 * @throws NoSuchFieldException 
	 * @throws IOException 
	 * @throws SecurityException 
	 */
	public final synchronized static SessionManager getInstance() throws IOException, SQLException {
	       if (instance == null) 
	    	   instance = new SessionManager();
	       return instance;
	}
	
	/**
	 * Retourne l'ensemble des données d'un objet de class c depuis la base de donnée
	 * à partir de sa clef.
	 * @param c type de l'objet à instancier
	 * @param key clef de l'object dans la base de donnée
	 * @throws SQLException 
	 */
	public List<List<String>> getData(Class c, String key) throws SQLException {
		
		List<List<String>> data = new LinkedList<List<String>>();
		List<String> variables = dbManager.getVariables(c, key);
		data.add(variables);
		
		EntityInfo info = (EntityInfo) c.getAnnotation(EntityInfo.class);
		List<Class> collections = Arrays.asList(info.collections());
		Iterator<Class> it = collections.iterator();
		while (it.hasNext()) {
			data.add(dbManager.getCollection(it.next(), info.rows()[0], key));
		}
		
		
		return data;
	}
	
	/**
	 * Sauvegarde la session en cours dans la base de données.
	 * Si tout se passe bien, {@link fr.univ_tln.inf9.exaplanning.api.db.SessionManager#entities entities} est purgé par un appel de
	 * {@link fr.univ_tln.inf9.exaplanning.api.db.SessionManager#purgeSession() SessionManager.purgeSession}.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws SQLException
	 * @throws IOException
	 * @throws NoSuchFieldException
	 */
	//TODO: nettoyer les exceptions
	public void saveSession() throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, SQLException, IOException, NoSuchFieldException {
		
		Set<Object> keys = entities.keySet();
		Iterator<Object> it = keys.iterator();
		
		while (it.hasNext()) {
			Entity e = entities.get(it.next());
			AbstractEntity o = (AbstractEntity) e.getObject();
			if (e.getType() == EntityType.INSERT) {
				DataBaseManager.getInstance().create(o);
			} else {
				DataBaseManager.getInstance().merge(o);
			}
		}
		
		try {
			DataBaseManager.getInstance().save();
			purgeSession();
		} catch (SQLException exception) {
			
			throw exception;
		}
	}
	
	private void purgeSession() {
		entities.clear();
	}

	@Override
	public void ObjectChanged(ObjectChangedEvent event) {
		Object o = event.getNewObject();
		
		if (!entities.containsKey(o)) {
			entities.put(o, new Entity(o, EntityType.UPDATE));
		}
	}

	@Override
	public void ObjectCreated(ObjectCreatedEvent event) {
		Object o = event.getNewObject();
		
		if (!entities.containsKey(o)) {
			entities.put(o, new Entity(o, EntityType.INSERT));
		}
	}
}
